/*! ******************************************************************************
 *
 * Pentaho
 *
 * Copyright (C) 2024 by Hitachi Vantara, LLC : http://www.pentaho.com
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file.
 *
 * Change Date: 2029-07-20
 ******************************************************************************/


package org.pentaho.test.platform.repository2.unified;

import static org.pentaho.platform.api.repository2.unified.RepositoryFilePermission.READ;
import static org.pentaho.platform.api.repository2.unified.RepositoryFilePermission.WRITE;
import static org.pentaho.platform.api.repository2.unified.RepositoryFileSid.Type.ROLE;
import static org.pentaho.platform.api.repository2.unified.RepositoryFileSid.Type.USER;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.pentaho.platform.api.locale.IPentahoLocale;
import org.pentaho.platform.api.mt.ITenantedPrincipleNameResolver;
import org.pentaho.platform.api.repository2.unified.IRepositoryFileData;
import org.pentaho.platform.api.repository2.unified.IUnifiedRepository;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.RepositoryFileAce;
import org.pentaho.platform.api.repository2.unified.RepositoryFileAcl;
import org.pentaho.platform.api.repository2.unified.RepositoryFilePermission;
import org.pentaho.platform.api.repository2.unified.RepositoryFileSid;
import org.pentaho.platform.api.repository2.unified.RepositoryFileTree;
import org.pentaho.platform.api.repository2.unified.RepositoryRequest;
import org.pentaho.platform.api.repository2.unified.UnifiedRepositoryException;
import org.pentaho.platform.api.repository2.unified.VersionSummary;
import org.pentaho.platform.api.repository2.unified.data.node.DataNode;
import org.pentaho.platform.api.repository2.unified.data.node.DataNode.DataPropertyType;
import org.pentaho.platform.api.repository2.unified.data.node.DataProperty;
import org.pentaho.platform.api.repository2.unified.data.node.NodeRepositoryFileData;
import org.pentaho.platform.api.repository2.unified.data.simple.SimpleRepositoryFileData;
import org.pentaho.platform.core.mt.Tenant;
import org.pentaho.platform.security.userroledao.DefaultTenantedPrincipleNameResolver;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * Mock implementation of the {@link IUnifiedRepository} for unit testing.
 * 
 * @author dkincade
 * @author mlowery
 */
@SuppressWarnings( "nls" )
public class MockUnifiedRepository implements IUnifiedRepository {

  // ~ Static fields/initializers
  // ======================================================================================

  // ~ Instance fields
  // =================================================================================================

  private IdManager idManager = new IdManager();

  private VersionManager versionManager = new VersionManager();

  private DeleteManager deleteManager = new DeleteManager();

  private LockManager lockManager = new LockManager();

  private ReferralManager referralManager = new ReferralManager();

  private ICurrentUserProvider currentUserProvider = new SpringSecurityCurrentUserProvider();

  private FileRecord root;

  private static ITenantedPrincipleNameResolver userNameUtils = new DefaultTenantedPrincipleNameResolver();

  // ~ Constructors
  // ====================================================================================================

  /**
   * Creates a mock.
   * 
   * @param currentUserProvider
   *          create your own or use {@link SpringSecurityCurrentUserProvider}.
   */
  public MockUnifiedRepository( final ICurrentUserProvider currentUserProvider ) {
    super();
    init();
    this.currentUserProvider = currentUserProvider;
  }

  // ~ Methods
  // =========================================================================================================

  public static RepositoryFileSid everyone() {
    return new RepositoryFileSid( userNameUtils.getPrincipleId( null, "__everyone__" ), ROLE );
  }

  public static RepositoryFileSid root() {
    return new RepositoryFileSid( userNameUtils.getPrincipleId( null, "__root__" ), USER );
  }

  protected void init() {
    RepositoryFile rootFolder =
        new RepositoryFile.Builder( "" ).path( RepositoryFile.SEPARATOR ).folder( true ).build();

    RepositoryFileAcl rootFolderAcl =
        new RepositoryFileAcl.Builder( root() ).entriesInheriting( false ).ace( everyone(), READ ).build();

    root = new FileRecord( rootFolder, rootFolderAcl );
    idManager.register( root );

    RepositoryFile publicFolder =
        new RepositoryFile.Builder( "public" ).path( RepositoryFile.SEPARATOR + "public" ).folder( true ).build();

    RepositoryFileAcl publicFolderAcl =
        new RepositoryFileAcl.Builder( root() ).entriesInheriting( false ).ace( everyone(), READ, WRITE ).build();

    FileRecord pub = new FileRecord( publicFolder, publicFolderAcl );
    root.addChild( pub );
    idManager.register( pub );

    RepositoryFile etcFolder =
        new RepositoryFile.Builder( "etc" ).path( RepositoryFile.SEPARATOR + "etc" ).folder( true ).build();

    RepositoryFileAcl etcFolderAcl = new RepositoryFileAcl.Builder( root() ).entriesInheriting( true ).build();

    FileRecord etc = new FileRecord( etcFolder, etcFolderAcl );
    root.addChild( etc );
    idManager.register( etc );

  }

  @Override
  public RepositoryFile getFile( final String path ) {
    FileRecord r = root.getFileRecord( path );
    if ( r != null ) {
      if ( !hasAccess( r.getFile().getId(), EnumSet.of( READ ) ) ) {
        return null;
      }
      return r.getFile();
    }
    return null;
  }

  @Override
  public RepositoryFileTree getTree( RepositoryRequest repositoryRequest ) {
    return getTree( repositoryRequest.getPath(), repositoryRequest.getDepth(), repositoryRequest.getChildNodeFilter(),
        repositoryRequest.isShowHidden() );
  }

  @Override
  public RepositoryFileTree getTree( final String path, final int depth, final String filter,
                                     final boolean showHidden ) {
    FileRecord r = root.getFileRecord( path );
    RepositoryFile rootFile = r.getFile();
    if ( ( !showHidden && rootFile.isHidden() ) || rootFile.isAclNode() ) {
      return null;
    }
    List<RepositoryFileTree> children;
    if ( depth != 0 ) {
      children = new ArrayList<RepositoryFileTree>();
      if ( rootFile.isFolder() ) {
        List<RepositoryFile> childrenTmp = getChildren( rootFile.getId(), filter );
        for ( RepositoryFile child : childrenTmp ) {
          RepositoryFileTree repositoryFileTree = getTree( child.getPath(), depth - 1, filter, showHidden );
          if ( repositoryFileTree != null ) {
            children.add( repositoryFileTree );
          }
        }
      }
      Collections.sort( children );
    } else {
      children = null;
    }
    return new RepositoryFileTree( rootFile, children );
  }

  @Override
  public RepositoryFile getFileAtVersion( final Serializable fileId, final Serializable versionId ) {
    return versionManager.getFileAtVersion( fileId, versionId ).getFile();
  }

  @Override
  public RepositoryFile getFileById( final Serializable fileId ) {
    if ( idManager.hasId( fileId ) ) {
      if ( !hasAccess( fileId, EnumSet.of( READ ) ) ) {
        return null;
      }
      return idManager.getFileById( fileId ).getFile();
    }

    return null;
  }

  @Override
  public RepositoryFile getFile( final String path, final boolean loadLocaleMaps ) {
    return getFile( path );
  }

  @Override
  public RepositoryFile getFileById( final Serializable fileId, final boolean loadLocaleMaps ) {
    return getFileById( fileId );
  }

  @Override
  public RepositoryFile getFile( String path, IPentahoLocale locale ) {
    return getFile( path );
  }

  @Override
  public RepositoryFile getFileById( Serializable fileId, IPentahoLocale locale ) {
    return getFileById( fileId );
  }

  @Override
  public RepositoryFile getFile( String path, boolean loadLocaleMaps, IPentahoLocale locale ) {
    return getFile( path );
  }

  @Override
  public RepositoryFile getFileById( Serializable fileId, boolean loadLocaleMaps, IPentahoLocale locale ) {
    return getFileById( fileId );
  }

  @Override
  @SuppressWarnings( "unchecked" )
  public <T extends IRepositoryFileData> T getDataForRead( final Serializable fileId, final Class<T> dataClass ) {
    FileRecord r = idManager.getFileById( fileId );
    return (T) r.getData();
  }

  @Override
  @SuppressWarnings( "unchecked" )
  public <T extends IRepositoryFileData> T getDataAtVersionForRead( final Serializable fileId,
      final Serializable versionId, final Class<T> dataClass ) {
    if ( versionId == null ) {
      return (T) versionManager.getLatestVersion( fileId ).getData();
    }

    return (T) versionManager.getFileAtVersion( fileId, versionId ).getData();
  }

  @Override
  public <T extends IRepositoryFileData> T getDataForExecute( final Serializable fileId, final Class<T> dataClass ) {
    return getDataForRead( fileId, dataClass );
  }

  @Override
  public <T extends IRepositoryFileData> T getDataAtVersionForExecute( final Serializable fileId,
      final Serializable versionId, final Class<T> dataClass ) {
    return getDataAtVersionForRead( fileId, versionId, dataClass );
  }

  @Override
  public <T extends IRepositoryFileData> List<T> getDataForReadInBatch( final List<RepositoryFile> files,
      final Class<T> dataClass ) {
    List<T> datas = new ArrayList<T>();
    for ( RepositoryFile file : files ) {
      if ( file.getVersionId() != null ) {
        datas.add( getDataAtVersionForRead( file.getId(), file.getVersionId(), dataClass ) );
      } else {
        datas.add( getDataForRead( file.getId(), dataClass ) );
      }
    }
    return datas;
  }

  @Override
  public <T extends IRepositoryFileData> List<T> getDataForExecuteInBatch( final List<RepositoryFile> files,
      final Class<T> dataClass ) {
    List<T> datas = new ArrayList<T>();
    for ( RepositoryFile file : files ) {
      if ( file.getVersionId() != null ) {
        datas.add( getDataAtVersionForExecute( file.getId(), file.getVersionId(), dataClass ) );
      } else {
        datas.add( getDataForExecute( file.getId(), dataClass ) );
      }
    }
    return datas;
  }

  @Override
  public RepositoryFile createFile( final Serializable parentFolderId, final RepositoryFile file,
      final IRepositoryFileData data, final String versionMessage ) {
    return createFile( parentFolderId, file, data, createDefaultAcl(), versionMessage );
  }

  private RepositoryFileAcl createDefaultAcl() {
    RepositoryFileAcl.Builder builder =
        new RepositoryFileAcl.Builder( userNameUtils.getPrincipleId( new Tenant( "/pentaho", true ),
            currentUserProvider.getUser() ) );
    builder.entriesInheriting( true );
    return builder.build();
  }

  @Override
  public RepositoryFile createFile( final Serializable parentFolderId, final RepositoryFile file,
      final IRepositoryFileData data, final RepositoryFileAcl acl, final String versionMessage ) {
    Validate.isTrue( !file.isFolder() );
    if ( !hasAccess( parentFolderId, EnumSet.of( WRITE ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    FileRecord parentFolder = idManager.getFileById( parentFolderId );
    RepositoryFile fileFromRepo =
        new RepositoryFile.Builder( file ).path( parentFolder.getPath() + RepositoryFile.SEPARATOR + file.getName() )
            .title( findTitle( file ) ).description( findDesc( file ) ).build();
    RepositoryFileAcl aclFromRepo = new RepositoryFileAcl.Builder( acl ).build();
    FileRecord fileRecord = new FileRecord( fileFromRepo, data, aclFromRepo, new HashMap<String, Serializable>() );
    idManager.register( fileRecord );

    process( fileRecord, null );

    parentFolder.addChild( fileRecord );
    if ( file.isVersioned() ) {
      versionManager.createVersion( fileRecord.getFile().getId(), currentUserProvider.getUser(), versionMessage,
          new Date() );
    }
    return fileRecord.getFile();
  }

  private void process( final FileRecord r, final IRepositoryFileData oldData ) {
    IRepositoryFileData data = r.getData();
    if ( data instanceof SimpleRepositoryFileData ) {
      r.setData( new ReusableSimpleRepositoryFileData( (SimpleRepositoryFileData) data ) );
    } else if ( data instanceof NodeRepositoryFileData ) {
      DataNode node = ( (NodeRepositoryFileData) data ).getNode();
      referralManager.process( r.getFile().getId(), oldData != null ? ( (NodeRepositoryFileData) oldData ).getNode()
          : null, node );
      r.setData( new NodeRepositoryFileData( idManager.process( node ) ) );
    }
  }

  private static String findTitle( final RepositoryFile file ) {
    String title = null;
    if ( file.getLocalePropertiesMap() != null ) {
      Properties properties = file.getLocalePropertiesMap().get( Locale.getDefault().toString() );
      if ( properties == null ) {
        properties = file.getLocalePropertiesMap().get( RepositoryFile.DEFAULT_LOCALE );
        if ( properties != null ) {
          title = properties.getProperty( RepositoryFile.FILE_TITLE );
          if ( StringUtils.isBlank( title ) ) {
            title = properties.getProperty( RepositoryFile.TITLE );
          }
        }
      }
    }

    return title;
  }

  private static String findDesc( final RepositoryFile file ) {
    String desc = null;
    if ( file.getLocalePropertiesMap() != null ) {
      Properties properties = file.getLocalePropertiesMap().get( Locale.getDefault().toString() );
      if ( properties == null ) {
        properties = file.getLocalePropertiesMap().get( RepositoryFile.DEFAULT_LOCALE );
        if ( properties != null ) {
          desc = properties.getProperty( RepositoryFile.FILE_DESCRIPTION );
          if ( StringUtils.isBlank( desc ) ) {
            desc = properties.getProperty( RepositoryFile.DESCRIPTION );
          }
        }
      }
    }

    return desc;
  }

  @Override
  public RepositoryFile createFolder( final Serializable parentFolderId, final RepositoryFile file,
      final String versionMessage ) {
    return createFolder( parentFolderId, file, createDefaultAcl(), versionMessage );
  }

  @Override
  public RepositoryFile createFolder( final Serializable parentFolderId, final RepositoryFile file,
      final RepositoryFileAcl acl, final String versionMessage ) {
    Validate.isTrue( file.isFolder() );
    Validate.isTrue( !file.isVersioned() );
    if ( !hasAccess( parentFolderId, EnumSet.of( WRITE ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    FileRecord parentFolder = idManager.getFileById( parentFolderId );
    RepositoryFile fileFromRepo =
        new RepositoryFile.Builder( file ).path(
            parentFolder.getPath()
                + ( parentFolder.getPath().endsWith( RepositoryFile.SEPARATOR ) ? "" : RepositoryFile.SEPARATOR )
                + file.getName() ).title( findTitle( file ) ).description( findDesc( file ) ).build();
    RepositoryFileAcl aclFromRepo = new RepositoryFileAcl.Builder( acl ).build();
    FileRecord fileRecord = new FileRecord( fileFromRepo, null, aclFromRepo, new HashMap<String, Serializable>() );
    idManager.register( fileRecord );
    parentFolder.addChild( fileRecord );
    return fileRecord.getFile();
  }

  @Override
  public List<RepositoryFile> getChildren( RepositoryRequest repositoryRequest ) {
    return getChildren( repositoryRequest.getPath(), repositoryRequest.getChildNodeFilter(), repositoryRequest.isShowHidden() );
  }

  @Override
  public List<RepositoryFile> getChildren( final Serializable folderId ) {
    return getChildren( folderId, null );
  }

  @Override
  public List<RepositoryFile> getChildren( final Serializable folderId, final String filter ) {
    return getChildren( folderId, filter, null );
  }

  @Override
  public List<RepositoryFile> getChildren( final Serializable folderId, final String filter, final Boolean showHiddenFiles ) {
    FileRecord r = idManager.getFileById( folderId );
    List<RepositoryFile> children = new ArrayList<RepositoryFile>();
    for ( FileRecord child : r.getChildren() ) {
      if ( filter != null ) {
        if ( matches( child.getFile().getName(), filter ) ) {
          if ( hasAccess( child.getFile().getId(), EnumSet.of( READ ) ) ) {
            children.add( child.getFile() );
          }
        }
      } else {
        if ( hasAccess( child.getFile().getId(), EnumSet.of( READ ) ) ) {
          children.add( child.getFile() );
        }
      }
    }
    return children;
  }

  private static boolean matches( final String in, final String pattern ) {
    StringBuilder buf = new StringBuilder();
    // build a regex
    String[] patterns = pattern.split( "\\|" );
    for ( int i = 0; i < patterns.length; i++ ) {
      if ( i > 0 ) {
        buf.append( "|" );
      }
      String tmp = patterns[i].trim();
      tmp = tmp.replace( ".", "\\." );
      tmp = tmp.replace( "*", ".*" );
      buf.append( tmp );
    }
    Pattern p = Pattern.compile( buf.toString() );
    Matcher m = p.matcher( in );
    return m.matches();
  }

  @Override
  public RepositoryFile updateFile( final RepositoryFile file, final IRepositoryFileData data,
      final String versionMessage ) {
    Validate.isTrue( !file.isFolder() );
    if ( !hasAccess( file.getId(), EnumSet.of( WRITE ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    FileRecord fileRecord = idManager.getFileById( file.getId() );
    fileRecord.setFile( new RepositoryFile.Builder( file ).title( findTitle( file ) ).description( findDesc( file ) )
        .build() );
    IRepositoryFileData oldData = fileRecord.getData();
    fileRecord.setData( data );

    process( fileRecord, oldData );

    if ( file.isVersioned() ) {
      versionManager.createVersion( fileRecord.getFile().getId(), currentUserProvider.getUser(), versionMessage,
          new Date() );
    }
    return fileRecord.getFile();
  }

  @Override
  public void deleteFile( final Serializable fileId, final boolean permanent, final String versionMessage ) {
    FileRecord r = idManager.getFileById( fileId );
    FileRecord parentFolder = r.getParent();
    orphanFile( fileId );
    if ( !permanent ) {
      deleteManager.trash( parentFolder.getPath(), r );
    } else {
      idManager.deregister( fileId );
    }
  }

  private void orphanFile( final Serializable fileId ) {
    FileRecord r = idManager.getFileById( fileId );
    FileRecord parentFolder = r.getParent();
    if ( !hasAccess( parentFolder.getFile().getId(), EnumSet.of( WRITE ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    parentFolder.orphan( r.getFile().getName() );
  }

  @Override
  public void deleteFile( final Serializable fileId, final String versionMessage ) {
    deleteFile( fileId, false, versionMessage );
  }

  @Override
  public void moveFile( final Serializable fileId, final String destAbsPath, final String versionMessage ) {
    copyMoveFile( fileId, destAbsPath, true );
  }

  @Override
  public void copyFile( final Serializable fileId, final String destAbsPath, final String versionMessage ) {
    copyMoveFile( fileId, destAbsPath, false );
  }

  private void copyMoveFile( final Serializable fileId, final String destAbsPath, final boolean move ) {
    FileRecord src = idManager.getFileById( fileId );
    FileRecord dest = root.getFileRecord( destAbsPath );
    String newName = src.getFile().getName();
    if ( dest != null ) {
      if ( !dest.getFile().isFolder() ) {
        throw new UnifiedRepositoryException( "file already exists" );
      }
    } else {
      // find parent folder
      String parentPath = StringUtils.substringBeforeLast( destAbsPath, RepositoryFile.SEPARATOR );
      FileRecord parent = root.getFileRecord( parentPath );
      if ( parent == null ) {
        throw new UnifiedRepositoryException( "invalid destination path" );
      }
      dest = parent;
      newName = StringUtils.substringAfterLast( destAbsPath, RepositoryFile.SEPARATOR );
    }
    String newPath =
        dest.getPath()
            + ( dest.getPath().endsWith( RepositoryFile.SEPARATOR ) ? newName : RepositoryFile.SEPARATOR + newName );

    FileRecord newChild =
        new FileRecord( new RepositoryFile.Builder( src.getFile() ).name( newName ).path( newPath ).build(), src
            .getData(), src.getAcl(), src.getMetadata() );
    if ( !move ) {
      idManager.register( newChild );
    }
    dest.addChild( newChild );
    if ( move ) {
      orphanFile( fileId );
    }
  }

  @Override
  public void undeleteFile( final Serializable fileId, final String versionMessage ) {
    FileRecord r = idManager.getFileById( fileId );
    FileRecord parentFolder = r.getParent();
    if ( !hasAccess( parentFolder.getFile().getId(), EnumSet.of( WRITE ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    deleteManager.restore( fileId );
  }

  @Override
  public List<RepositoryFile> getDeletedFiles( final String origParentFolderPath ) {
    return getDeletedFiles( origParentFolderPath, null );
  }

  @Override
  public List<RepositoryFile> getDeletedFiles( final String origParentFolderPath, final String filter ) {
    List<RepositoryFile> deletedFiles = new ArrayList<RepositoryFile>();
    List<FileRecord> d = deleteManager.getTrashedFiles( origParentFolderPath, filter );
    for ( FileRecord r : d ) {
      deletedFiles.add( r.getFile() );
    }
    return deletedFiles;
  }

  @Override
  public List<RepositoryFile> getDeletedFiles() {
    List<RepositoryFile> deletedFiles = new ArrayList<RepositoryFile>();
    List<FileRecord> d = deleteManager.getTrashedFiles();
    for ( FileRecord r : d ) {
      deletedFiles.add( r.getFile() );
    }
    return deletedFiles;
  }

  @Override
  public boolean canUnlockFile( final Serializable fileId ) {
    return lockManager.canUnlockFile( fileId );
  }

  @Override
  public void lockFile( final Serializable fileId, final String message ) {
    lockManager.lockFile( fileId, message );
  }

  @Override
  public void unlockFile( final Serializable fileId ) {
    lockManager.unlockFile( fileId );
  }

  @Override
  public RepositoryFileAcl getAcl( final Serializable fileId ) {
    if ( !hasAccess( fileId, EnumSet.of( READ ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    FileRecord r = idManager.getFileById( fileId );
    return r.getAcl();
  }

  @Override
  public RepositoryFileAcl updateAcl( final RepositoryFileAcl acl ) {
    if ( !hasAccess( acl.getId(), EnumSet.of( WRITE ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    FileRecord r = idManager.getFileById( acl.getId() );
    r.setAcl( acl );
    return acl;
  }

  @Override
  public boolean hasAccess( final String path, final EnumSet<RepositoryFilePermission> permissions ) {
    FileRecord r = root.getFileRecord( path );
    if ( r != null ) {
      return hasAccess( r.getFile().getId(), permissions );
    }
    return false;
  }

  private boolean hasAccess( final Serializable fileId, final EnumSet<RepositoryFilePermission> permissions ) {
    String username = currentUserProvider.getUser();
    List<String> roles = currentUserProvider.getRoles();

    RepositoryFileAcl acl = idManager.getFileById( fileId ).getAcl();
    if ( acl.getOwner().getType() == USER && acl.getOwner().getName().equals( username ) ) {
      return true; // owner can do anything
    }

    List<RepositoryFileAce> aces = internalGetEffectiveAces( fileId );
    for ( RepositoryFileAce ace : aces ) {
      if ( ace.getSid().equals( everyone() ) && ace.getPermissions().containsAll( permissions ) ) {
        return true; // match special everyone role
      } else if ( ace.getSid().getType() == USER && ace.getSid().getName().equals( username )
          && ace.getPermissions().containsAll( permissions ) ) {
        return true; // match on user
      }
      for ( String role : roles ) {
        if ( ace.getSid().getType() == ROLE && ace.getSid().getName().equals( role )
            && ace.getPermissions().containsAll( permissions ) ) {
          return true; // match on role
        }
      }
    }
    return false;
  }

  @Override
  public List<RepositoryFileAce> getEffectiveAces( final Serializable fileId ) {
    if ( !hasAccess( fileId, EnumSet.of( READ ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    return internalGetEffectiveAces( fileId );
  }

  private List<RepositoryFileAce> internalGetEffectiveAces( final Serializable fileId ) {
    FileRecord r = idManager.getFileById( fileId );
    if ( r.getParent() == null ) {
      return r.getAcl().getAces();
    } else if ( r.getAcl().isEntriesInheriting() == false ) {
      return r.getAcl().getAces();
    } else {
      return getEffectiveAces( r.getParent().getFile().getId() );
    }
  }

  @Override
  public List<RepositoryFileAce> getEffectiveAces( final Serializable fileId, final boolean forceEntriesInheriting ) {
    FileRecord r = idManager.getFileById( fileId );
    if ( r.getParent() != null ) {
      return getEffectiveAces( r.getParent().getFile().getId() );
    }

    return r.getAcl().getAces();
  }

  @Override
  public VersionSummary getVersionSummary( final Serializable fileId, final Serializable versionId ) {
    FrozenFileRecord r = null;
    if ( versionId == null ) {
      r = versionManager.getLatestVersion( fileId );
    } else {
      r = versionManager.getFileAtVersion( fileId, versionId );
    }
    return new VersionSummary( r.getVersionId(), r.getFile().getId(), false, r.getDate(), r.getAuthor(), r
        .getVersionMessage(), new ArrayList<String>( 0 ) );
  }

  @Override
  public List<VersionSummary> getVersionSummaryInBatch( final List<RepositoryFile> files ) {
    List<VersionSummary> sums = new ArrayList<VersionSummary>();
    for ( RepositoryFile file : files ) {
      if ( file.getVersionId() != null ) {
        sums.add( getVersionSummary( file.getId(), file.getVersionId() ) );
      } else {
        sums.add( getVersionSummary( file.getId(), versionManager.getLatestVersion( file.getId() ).getVersionId() ) );
      }
    }
    return sums;
  }

  @Override
  public List<VersionSummary> getVersionSummaries( final Serializable fileId ) {
    List<VersionSummary> sums = new ArrayList<VersionSummary>();
    List<FrozenFileRecord> records = versionManager.getVersions( fileId );
    for ( FrozenFileRecord record : records ) {
      sums.add( new VersionSummary( record.getVersionId(), record.getFile().getId(), false, record.getDate(), record
          .getAuthor(), record.getVersionMessage(), new ArrayList<String>( 0 ) ) );
    }
    return sums;
  }

  @Override
  public void deleteFileAtVersion( final Serializable fileId, final Serializable versionId ) {
    versionManager.deleteVersion( fileId, versionId );
  }

  @Override
  public void
    restoreFileAtVersion( final Serializable fileId, final Serializable versionId, final String versionMessage ) {
    FrozenFileRecord restored =
        versionManager.restoreVersion( fileId, versionId, currentUserProvider.getUser(), versionMessage, new Date() );
    FileRecord fileRecord = idManager.getFileById( fileId );
    fileRecord.setData( restored.getData() );
    fileRecord.setMetadata( restored.getMetadata() );
    // reset properties that aren't versioned
    RepositoryFile orig = fileRecord.getFile();
    fileRecord.setFile( new RepositoryFile.Builder( restored.getFile() ).locked( orig.isLocked() ).lockDate(
        orig.getLockDate() ).lockMessage( orig.getLockMessage() ).lockOwner( orig.getLockOwner() ).build() );
  }

  @Override
  public List<RepositoryFile> getReferrers( final Serializable fileId ) {
    List<RepositoryFile> files = new ArrayList<RepositoryFile>();
    for ( Serializable refFileId : referralManager.getReferrers( fileId ) ) {
      files.add( idManager.getFileById( refFileId ).getFile() );
    }
    return files;
  }

  @Override
  public void setFileMetadata( final Serializable fileId, final Map<String, Serializable> metadataMap ) {
    // if (!hasAccess(fileId, EnumSet.of(WRITE))) {
    // throw new AccessDeniedException("access denied");
    // }
    FileRecord r = idManager.getFileById( fileId );
    r.setMetadata( metadataMap );
  }

  @Override
  public Map<String, Serializable> getFileMetadata( final Serializable fileId ) {
    if ( !hasAccess( fileId, EnumSet.of( READ ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    FileRecord r = idManager.getFileById( fileId );
    return r.getMetadata();
  }

  // ~ Helper classes
  // ==================================================================================================

  @SuppressWarnings( "serial" )
  private class ReusableSimpleRepositoryFileData extends SimpleRepositoryFileData {

    private byte[] bytes;

    public ReusableSimpleRepositoryFileData( final SimpleRepositoryFileData data ) {
      super( null, data.getEncoding(), data.getMimeType() );
      try {
        bytes = IOUtils.toByteArray( data.getStream() );
      } catch ( IOException e ) {
        throw new RuntimeException( e );
      }
    }

    @Override
    public InputStream getStream() {
      return new ByteArrayInputStream( bytes );
    }

  }

  private class FileRecord implements Comparable<FileRecord> {

    public FileRecord( final RepositoryFile file, final IRepositoryFileData data, final RepositoryFileAcl acl,
        final Map<String, Serializable> metadata ) {
      this.file = file;
      setData( data );
      this.acl = acl;
      setMetadata( metadata );
    }

    public FileRecord( final RepositoryFile file, final RepositoryFileAcl acl ) {
      this( file, null, acl, new HashMap<String, Serializable>() );
    }

    public void setFile( final RepositoryFile file ) {
      this.file = file;
    }

    public void setData( final IRepositoryFileData data ) {
      this.data = data;
    }

    public void setAcl( final RepositoryFileAcl acl ) {
      this.acl = acl;
    }

    public void setMetadata( final Map<String, Serializable> metadata ) {
      this.metadata = new HashMap<String, Serializable>( metadata );
    }

    private void setParent( final FileRecord parent ) {
      this.parent = parent;
    }

    private FileRecord getParent() {
      return parent;
    }

    public RepositoryFileAcl getAcl() {
      return acl;
    }

    public String getPath() {
      if ( file.getName().equals( "" ) ) {
        return RepositoryFile.SEPARATOR;
      }
      String parentPath = parent.getPath();
      return parentPath + ( parentPath.endsWith( RepositoryFile.SEPARATOR ) ? "" : RepositoryFile.SEPARATOR )
          + file.getName();
    }

    public FileRecord getFileRecord( final String path ) {
      String normalizedPath = path;
      if ( path.startsWith( RepositoryFile.SEPARATOR ) ) {
        normalizedPath = path.substring( 1 );
      }
      if ( normalizedPath.equals( "" ) ) {
        return this;
      }
      String[] pathSegments = normalizedPath.split( RepositoryFile.SEPARATOR );
      if ( hasChild( pathSegments[0] ) ) {
        return getChild( pathSegments[0] ).getFileRecord(
            1 <= pathSegments.length - 1 ? StringUtils.join(
                Arrays.copyOfRange( pathSegments, 1, pathSegments.length ), RepositoryFile.SEPARATOR ) : "" );
      }
      return null;
    }

    public boolean hasChild( final String name ) {
      return getChild( name ) != null;
    }

    public FileRecord getChild( final String name ) {
      for ( FileRecord child : children ) {
        if ( child.getFile().getName().equals( name ) ) {
          return child;
        }
      }
      return null;
    }

    private RepositoryFile file;

    private IRepositoryFileData data;

    private Map<String, Serializable> metadata;

    public FileRecord parent;

    public List<FileRecord> children = new ArrayList<FileRecord>();

    public RepositoryFileAcl acl;

    public void addChild( final FileRecord fileRecord ) {
      // make sure no name collision
      for ( FileRecord child : children ) {
        if ( child.getFile().getName().equals( fileRecord.getFile().getName() ) ) {
          throw new UnifiedRepositoryException( String.format( "file [%s] already exists", fileRecord.getFile()
              .getName() ) );
        }
      }
      children.add( fileRecord );
      fileRecord.setParent( this );
      Collections.sort( children );
    }

    public List<FileRecord> getChildren() {
      return Collections.unmodifiableList( children );
    }

    public void orphan( final String name ) {
      for ( Iterator<FileRecord> iter = children.iterator(); iter.hasNext(); ) {
        FileRecord r = iter.next();
        if ( r.getFile().getName().equals( name ) ) {
          iter.remove();
          return;
        }
      }
    }

    private RepositoryFile getFile() {
      return file;
    }

    private IRepositoryFileData getData() {
      return data;
    }

    private Map<String, Serializable> getMetadata() {
      return Collections.unmodifiableMap( metadata );
    }

    @Override
    public int compareTo( final FileRecord other ) {
      return this.getFile().getName().compareTo( other.getFile().getName() );
    }

  }

  private class IdManager {

    private Map<Serializable, FileRecord> idMap = new HashMap<Serializable, FileRecord>();

    public boolean hasId( final Serializable fileId ) {
      return idMap.containsKey( fileId );
    }

    public FileRecord getFileById( final Serializable fileId ) {
      FileRecord r = idMap.get( fileId );
      if ( r == null ) {
        throw new UnifiedRepositoryException( String.format( "file id [%s] does not exist", fileId ) );
      }
      return r;
    }

    public void register( final FileRecord fileRecord ) {
      Serializable fileId = UUID.randomUUID().toString();
      fileRecord.setFile( new RepositoryFile.Builder( fileRecord.getFile() ).id( fileId ).build() );
      fileRecord.setAcl( new RepositoryFileAcl.Builder( fileRecord.getAcl() ).id( fileId ).build() );
      idMap.put( fileId, fileRecord );
    }

    public DataNode process( final DataNode node ) {
      node.setId( UUID.randomUUID().toString() );
      for ( DataNode child : node.getNodes() ) {
        process( child );
      }
      return node;
    }

    /**
     * Removes file from this manager. Returns FileRecord mapped to fileId (null if no such fileId).
     */
    public FileRecord deregister( final Serializable fileId ) {
      return idMap.remove( fileId );
    }

  }

  private class VersionManager {

    private Map<Serializable, List<FrozenFileRecord>> versionMap = new HashMap<Serializable, List<FrozenFileRecord>>();

    public FrozenFileRecord getLatestVersion( final Serializable fileId ) {
      List<FrozenFileRecord> history = versionMap.get( fileId );
      if ( history == null ) {
        throw new UnifiedRepositoryException( String.format( "version history for [%s] does not exist", fileId ) );
      }
      return history.get( history.size() - 1 );
    }

    public void createVersion( final Serializable fileId, final String author, final String versionMessage,
        final Date date ) {
      List<FrozenFileRecord> history = versionMap.get( fileId );
      if ( history == null ) {
        history = new ArrayList<FrozenFileRecord>();
        versionMap.put( fileId, history );
      }
      FileRecord fileRecord = idManager.getFileById( fileId );
      fileRecord.setFile( new RepositoryFile.Builder( fileRecord.getFile() ).versionId( history.size() ).build() );
      history.add( new FrozenFileRecord( history.size(), fileRecord.getFile(), fileRecord.getData(), fileRecord
          .getMetadata(), author, versionMessage, date ) );
    }

    public void deleteVersion( final Serializable fileId, final Serializable versionId ) {
      List<FrozenFileRecord> history = versionMap.get( fileId );
      if ( history == null ) {
        throw new UnifiedRepositoryException( String.format( "version history for [%s] does not exist", fileId ) );
      }
      Integer versionNumber = versionNumber( versionId );
      if ( versionNumber >= 0 && versionNumber < history.size() ) {
        FrozenFileRecord r = history.get( versionNumber );
        if ( r == null ) {
          throw new UnifiedRepositoryException( String.format( "version [%s] does not exist", versionId ) );
        }
        history.set( versionNumber, null );
      } else {
        throw new UnifiedRepositoryException( String.format( "unknown version [%s]", fileId ) );
      }
    }

    public FrozenFileRecord restoreVersion( final Serializable fileId, final Serializable versionId,
        final String author, final String versionMessage, final Date date ) {
      List<FrozenFileRecord> history = versionMap.get( fileId );
      if ( history == null ) {
        throw new UnifiedRepositoryException( String.format( "version history for [%s] does not exist", fileId ) );
      }
      Integer versionNumber = versionNumber( versionId );
      if ( versionNumber >= 0 && versionNumber < history.size() ) {
        FrozenFileRecord r = history.get( versionNumber );
        if ( r == null ) {
          throw new UnifiedRepositoryException( String.format( "version [%s] does not exist", versionId ) );
        }
        history.add( new FrozenFileRecord( history.size(), new RepositoryFile.Builder( r.getFile() ).versionId(
            history.size() ).build(), r.getData(), r.getMetadata(), author, versionMessage, date ) );
        return history.get( history.size() - 1 );
      }

      throw new UnifiedRepositoryException( String.format( "unknown version [%s]", fileId ) );
    }

    public List<FrozenFileRecord> getVersions( final Serializable fileId ) {
      List<FrozenFileRecord> history = versionMap.get( fileId );
      if ( history == null ) {
        throw new UnifiedRepositoryException( String.format( "version history for [%s] does not exist", fileId ) );
      }
      List<FrozenFileRecord> cleanedHistory = new ArrayList<FrozenFileRecord>( history );
      for ( Iterator<FrozenFileRecord> iter = cleanedHistory.iterator(); iter.hasNext(); ) {
        FrozenFileRecord r = iter.next();
        if ( r == null ) {
          iter.remove();
        }
      }
      return cleanedHistory;
    }

    public FrozenFileRecord getFileAtVersion( final Serializable fileId, final Serializable versionId ) {
      List<FrozenFileRecord> history = versionMap.get( fileId );
      if ( history == null ) {
        throw new UnifiedRepositoryException( String.format( "version history for [%s] does not exist", fileId ) );
      }
      Integer versionNumber = versionNumber( versionId );
      FrozenFileRecord r = history.get( versionNumber );
      if ( r == null ) {
        throw new UnifiedRepositoryException( String.format( "version [%s] does not exist", versionId ) );
      }
      return r;
    }

    private int versionNumber( final Serializable versionId ) {
      if ( versionId instanceof Integer ) {
        return (Integer) versionId;
      } else if ( versionId instanceof String ) {
        return Integer.parseInt( (String) versionId );
      } else {
        return Integer.parseInt( versionId.toString() );
      }
    }

  }

  public class FrozenFileRecord {

    private Serializable versionId;

    private RepositoryFile file;

    private IRepositoryFileData data;

    private Map<String, Serializable> metadata;

    private String author;

    private String versionMessage;

    private Date date;

    public FrozenFileRecord( final Serializable versionId, final RepositoryFile file, final IRepositoryFileData data,
        final Map<String, Serializable> metadata, final String author, final String versionMessage, final Date date ) {
      super();
      this.versionId = versionId;
      this.file = file;
      this.data = data;
      this.metadata = metadata;
      this.author = author;
      this.versionMessage = versionMessage;
      this.date = date;
    }

    public Serializable getVersionId() {
      return versionId;
    }

    public RepositoryFile getFile() {
      return file;
    }

    public IRepositoryFileData getData() {
      return data;
    }

    public Map<String, Serializable> getMetadata() {
      return metadata;
    }

    private String getAuthor() {
      return author;
    }

    private String getVersionMessage() {
      return versionMessage;
    }

    private Date getDate() {
      return date;
    }

  }

  private class DeleteManager {
    Map<String, Trash> userToTrashMap = new HashMap<String, Trash>();

    private class Trash {
      private Map<String, List<FileRecord>> origPathToFilesMap = new HashMap<String, List<FileRecord>>();

      private Map<Serializable, String> idToOrigPathMap = new HashMap<Serializable, String>();

      private Map<String, List<FileRecord>> getOrigPathToFilesMap() {
        return origPathToFilesMap;
      }

      private Map<Serializable, String> getIdToOrigPathMap() {
        return idToOrigPathMap;
      }
    }

    public void trash( final String origPath, final FileRecord deletedRecord ) {
      Trash trash = userToTrashMap.get( currentUserProvider.getUser() );
      if ( trash == null ) {
        trash = new Trash();
        userToTrashMap.put( currentUserProvider.getUser(), trash );
      }

      RepositoryFile popFile =
          new RepositoryFile.Builder( deletedRecord.getFile() ).originalParentFolderPath( origPath ).deletedDate(
              new Date() ).build();
      deletedRecord.setFile( popFile );

      List<FileRecord> dels = trash.getOrigPathToFilesMap().get( origPath );
      if ( dels == null ) {
        dels = new ArrayList<FileRecord>();
        trash.getOrigPathToFilesMap().put( origPath, dels );
      }
      dels.add( deletedRecord );
      trash.getIdToOrigPathMap().put( deletedRecord.getFile().getId(), origPath );
    }

    public void restore( final Serializable fileId ) {
      Trash trash = userToTrashMap.get( currentUserProvider.getUser() );
      if ( trash == null ) {
        throw new UnifiedRepositoryException( "no trash found for user" );
      }

      String origPath = trash.getIdToOrigPathMap().get( fileId );
      List<FileRecord> dels = trash.getOrigPathToFilesMap().get( origPath );
      FileRecord found = null;
      for ( Iterator<FileRecord> iter = dels.iterator(); iter.hasNext(); ) {
        FileRecord r = iter.next();
        if ( r.getFile().getId().equals( fileId ) ) {
          iter.remove();
          found = r;
        }
      }
      FileRecord parentFolder = root.getFileRecord( origPath );
      parentFolder.addChild( found );
      trash.getIdToOrigPathMap().remove( fileId );

      RepositoryFile popFile =
          new RepositoryFile.Builder( found.getFile() ).originalParentFolderPath( null ).deletedDate( null ).build();
      found.setFile( popFile );
    }

    public List<FileRecord> getTrashedFiles( final String path, final String filter ) {
      Trash trash = userToTrashMap.get( currentUserProvider.getUser() );
      if ( trash == null ) {
        return Collections.emptyList();
      }

      List<FileRecord> filtered = new ArrayList<FileRecord>();
      List<FileRecord> dels = trash.getOrigPathToFilesMap().get( path );
      if ( dels == null ) {
        return Collections.emptyList();
      }
      for ( FileRecord d : dels ) {
        if ( filter != null ) {
          if ( matches( d.getFile().getName(), filter ) ) {
            filtered.add( d );
          }
        } else {
          filtered.add( d );
        }
      }
      return filtered;
    }

    public List<FileRecord> getTrashedFiles() {
      Trash trash = userToTrashMap.get( currentUserProvider.getUser() );
      if ( trash == null ) {
        return Collections.emptyList();
      }

      List<FileRecord> deletedRecords = new ArrayList<FileRecord>();
      for ( List<FileRecord> list : trash.getOrigPathToFilesMap().values() ) {
        deletedRecords.addAll( list );
      }
      Collections.sort( deletedRecords );
      return deletedRecords;
    }

  }

  private class LockManager {

    public boolean canUnlockFile( final Serializable fileId ) {
      return hasAccess( fileId, EnumSet.of( WRITE ) );
    }

    public void lockFile( final Serializable fileId, final String message ) {
      if ( !hasAccess( fileId, EnumSet.of( WRITE ) ) ) {
        throw new UnifiedRepositoryException( "access denied" );
      }
      FileRecord r = idManager.getFileById( fileId );
      if ( r.getFile().getLockOwner() != null ) {
        throw new IllegalStateException( "file is already locked" );
      }
      r.setFile( new RepositoryFile.Builder( r.getFile() ).lockOwner( currentUserProvider.getUser() ).lockDate(
          new Date() ).lockMessage( message ).locked( true ).build() );
    }

    public void unlockFile( final Serializable fileId ) {
      if ( !hasAccess( fileId, EnumSet.of( WRITE ) ) ) {
        throw new UnifiedRepositoryException( "access denied" );
      }
      FileRecord r = idManager.getFileById( fileId );
      if ( r.getFile().getLockOwner() == null ) {
        throw new IllegalStateException( "file is not locked" );
      }
      r.setFile( new RepositoryFile.Builder( r.getFile() ).lockOwner( null ).lockDate( null ).lockMessage( null )
          .locked( false ).build() );
    }

  }

  private class ReferralManager {

    private Map<Serializable, Set<Serializable>> referrersMap = new HashMap<Serializable, Set<Serializable>>();

    public void process( final Serializable fileId, final DataNode oldNode, final DataNode newNode ) {
      if ( oldNode != null ) {
        process( fileId, oldNode, true );
      }
      process( fileId, newNode, false );
    }

    private void process( final Serializable fileId, final DataNode node, final boolean clean ) {
      for ( DataProperty property : node.getProperties() ) {
        if ( property.getType() == DataPropertyType.REF ) {
          Set<Serializable> referrers = referrersMap.get( property.getRef().getId() );
          if ( referrers == null ) {
            referrers = new HashSet<Serializable>();
            referrersMap.put( property.getRef().getId(), referrers );
          }
          if ( clean ) {
            referrers.remove( fileId );
          } else {
            referrers.add( fileId );
          }
        }
      }
      for ( DataNode child : node.getNodes() ) {
        process( fileId, child, clean );
      }
    }

    public Set<Serializable> getReferrers( final Serializable fileId ) {
      Set<Serializable> refs = referrersMap.get( fileId );
      if ( refs == null ) {
        return Collections.emptySet();
      }
      return Collections.unmodifiableSet( refs );
    }

  }

  public static interface ICurrentUserProvider {
    public String getUser();

    public List<String> getRoles();
  }

  public static class SpringSecurityCurrentUserProvider implements ICurrentUserProvider {

    @Override
    public String getUser() {
      return SecurityContextHolder.getContext().getAuthentication().getName();
    }

    @Override
    public List<String> getRoles() {
      Collection<? extends GrantedAuthority> auths = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
      List<String> roles = new ArrayList<String>();
      for ( GrantedAuthority auth : auths ) {
        roles.add( auth.getAuthority() );
      }
      return roles;
    }

  }

  @Override
  public List<Character> getReservedChars() {
    return Collections.emptyList();
  }

  @Override
  public List<Locale> getAvailableLocalesForFileById( Serializable fileId ) {
    return Collections.emptyList();
  }

  @Override
  public List<Locale> getAvailableLocalesForFileByPath( String relPath ) {
    return Collections.emptyList();
  }

  @Override
  public List<Locale> getAvailableLocalesForFile( RepositoryFile repositoryFile ) {
    return Collections.emptyList();
  }

  @Override
  public Properties getLocalePropertiesForFileById( Serializable fileId, String locale ) {
    return new Properties();
  }

  @Override
  public Properties getLocalePropertiesForFileByPath( String relPath, String locale ) {
    return new Properties();
  }

  @Override
  public Properties getLocalePropertiesForFile( RepositoryFile repositoryFile, String locale ) {
    return new Properties();
  }

  @Override
  public void setLocalePropertiesForFileById( Serializable fileId, String locale, Properties properties ) {
  }

  @Override
  public void setLocalePropertiesForFileByPath( String relPath, String locale, Properties properties ) {
  }

  @Override
  public void setLocalePropertiesForFile( RepositoryFile repositoryFile, String locale, Properties properties ) {
  }

  @Override
  public void deleteLocalePropertiesForFile( RepositoryFile repositoryFile, String locale ) {
  }

  @Override
  public RepositoryFile updateFolder( RepositoryFile folder, String versionMessage ) {
    Validate.isTrue( folder.isFolder() );
    if ( !hasAccess( folder.getId(), EnumSet.of( WRITE ) ) ) {
      throw new AccessDeniedException( "access denied" );
    }
    FileRecord fileRecord = idManager.getFileById( folder.getId() );
    fileRecord.setFile( new RepositoryFile.Builder( folder ).hidden( folder.isHidden() ).title( findTitle( folder ) )
        .description( findDesc( folder ) ).aclNode( folder.isAclNode() ).build() );
    if ( folder.isVersioned() ) {
      versionManager.createVersion( fileRecord.getFile().getId(), currentUserProvider.getUser(), versionMessage,
          new Date() );
    }
    return fileRecord.getFile();
  }

  // public static void main(final String[] args) throws Exception {
  // MockUnifiedRepository repo = new MockUnifiedRepository();
  // t(repo.getFile("/").getId() != null);
  // t(repo.getFile("/").getPath().equals("/"));
  // t(repo.getFile("/public").getId() != null);
  // t(repo.getFile("/public").getPath().equals("/public"));
  // repo.createFile(repo.getFile("/public").getId(), new
  // RepositoryFile.Builder("file.txt").versioned(true).build(),
  // new SimpleRepositoryFileData(new ByteArrayInputStream("hello world".getBytes("UTF-8")), "UTF-8",
  // "text/plain"),
  // "hello world");
  // t(repo.getFile("/public/file.txt").getId() != null);
  // t(repo.getVersionSummaries(repo.getFile("/public/file.txt").getId()).size() == 1);
  //
  // repo.updateFile(repo.getFile("/public/file.txt"), new SimpleRepositoryFileData(new ByteArrayInputStream(
  // "caio world".getBytes("UTF-8")), "UTF-8", "text/plain"), "caio world");
  // t(repo.getVersionSummaries(repo.getFile("/public/file.txt").getId()).size() == 2);
  // List<VersionSummary> sums = repo.getVersionSummaries(repo.getFile("/public/file.txt").getId());
  // t(repo.getFileAtVersion(sums.get(0).getVersionedFileId(), sums.get(0).getId()) != null);
  // t(repo.getFileAtVersion(sums.get(1).getVersionedFileId(), sums.get(1).getId()) != null);
  // SimpleRepositoryFileData d = repo.getDataForRead(repo.getFile("/public/file.txt").getId(),
  // SimpleRepositoryFileData.class);
  // SimpleRepositoryFileData d0 = repo.getDataAtVersionForRead(repo.getFile("/public/file.txt").getId(),
  // sums.get(0)
  // .getId(), SimpleRepositoryFileData.class);
  // SimpleRepositoryFileData d1 = repo.getDataAtVersionForRead(repo.getFile("/public/file.txt").getId(),
  // sums.get(1)
  // .getId(), SimpleRepositoryFileData.class);
  // t(Arrays.equals(IOUtils.toByteArray(d.getStream()), IOUtils.toByteArray(d1.getStream())));
  // t(Arrays.equals(IOUtils.toByteArray(d0.getStream()), "hello world".getBytes("UTF-8")));
  // t(Arrays.equals(IOUtils.toByteArray(d1.getStream()), "caio world".getBytes("UTF-8")));
  //
  // repo.restoreFileAtVersion(repo.getFile("/public/file.txt").getId(), sums.get(0).getId(), null);
  // List<VersionSummary> sums2 = repo.getVersionSummaries(repo.getFile("/public/file.txt").getId());
  // t(sums2.size() == 3);
  // SimpleRepositoryFileData d2 = repo.getDataForRead(repo.getFile("/public/file.txt").getId(),
  // SimpleRepositoryFileData.class);
  // t(Arrays.equals(IOUtils.toByteArray(d2.getStream()), "hello world".getBytes("UTF-8")));
  // repo.deleteFileAtVersion(repo.getFile("/public/file.txt").getId(), sums2.get(2).getId());
  // t(repo.getVersionSummaries(repo.getFile("/public/file.txt").getId()).size() == 2);
  //
  // t(repo.getAcl(repo.getFile("/public/file.txt").getId()) != null);
  // RepositoryFileAcl newAcl = new
  // RepositoryFileAcl.Builder(repo.getAcl(repo.getFile("/public/file.txt").getId()))
  // .ace(new RepositoryFileSid("larry", USER), READ).build();
  // t(repo.updateAcl(newAcl).getId() != null);
  // t(repo.getAcl(repo.getFile("/public/file.txt").getId()).getAces().get(0).getSid()
  // .equals(new RepositoryFileSid("larry", USER)));
  //
  // t(repo.createFolder(repo.getFile("/public").getId(), new
  // RepositoryFile.Builder("testFolder").folder(true).build(),
  // null).getId() != null);
  //
  // t(repo.getChildren(repo.getFile("/public").getId()).size() == 2);
  // t(repo.getChildren(repo.getFile("/public").getId(), "*.txt").size() == 1);
  //
  // RepositoryFileTree tree = repo.getTree("/public", -1, null, true);
  // t(tree.getFile().getName().equals("public"));
  // t(tree.getChildren().size() == 2);
  // t(tree.getChildren().get(1).getFile().getName().equals("testFolder"));
  //
  // repo.copyFile(repo.getFile("/public/file.txt").getId(), "/public/testFolder", null);
  // t(repo.getFile("/public/file.txt") != null);
  // t(repo.getFile("/public/testFolder/file.txt") != null);
  // t(!repo.getFile("/public/file.txt").getId().equals(repo.getFile("/public/testFolder/file.txt").getId()));
  // Serializable testFolderFileId = repo.getFile("/public/testFolder/file.txt").getId();
  // repo.moveFile(testFolderFileId, "/public/file2.txt", null);
  // t(repo.getFile("/public/testFolder/file.txt") == null);
  // t(repo.getFile("/public/file2.txt").getId().equals(testFolderFileId));
  //
  // t(repo.canUnlockFile(repo.getFile("/public/file.txt").getId()));
  // t(!repo.getFile("/public/file.txt").isLocked());
  // repo.lockFile(repo.getFile("/public/file.txt").getId(), "blah");
  // t(repo.getFile("/public/file.txt").isLocked());
  // t(repo.getFile("/public/file.txt").getLockMessage().equals("blah"));
  // repo.unlockFile(repo.getFile("/public/file.txt").getId());
  // t(!repo.getFile("/public/file.txt").isLocked());
  //
  // t(repo.getDeletedFiles().isEmpty());
  // Serializable fileTxtId = repo.getFile("/public/file.txt").getId();
  // repo.deleteFile(repo.getFile("/public/file.txt").getId(), null);
  // t(repo.getDeletedFiles().size() == 1);
  // t(repo.getDeletedFiles("/public").size() == 1);
  // t(repo.getDeletedFiles("/public", "*.txt").size() == 1);
  // repo.undeleteFile(fileTxtId, null);
  // t(repo.getDeletedFiles().isEmpty());
  // repo.deleteFile(repo.getFile("/public/file.txt").getId(), true, null);
  // t(repo.getDeletedFiles().isEmpty());
  // }
  //
  // private static void t(boolean test) {
  // if (!test) {
  // throw new RuntimeException();
  // }
  // }
}
